/**
 * Ammo.js 工具箱
 */
import settings from "../Settings.js";

import {
  Euler,
  Quaternion,
  Vector3,
} from "../3rdParty/three.js/build/three.module.js";


const AmmoHelper = {};
let worldTransform;

/**
 * 物理引擎常量与变量
 */

const ActivationStates = {
  ACTIVE_TAG : 1,
  ISLAND_SLEEPING : 2,
  WANTS_DEACTIVATION : 3,
  DISABLE_DEACTIVATION : 4,
  DISABLE_SIMULATION  : 5,
}

const CollisionFlags = {
  CF_STATIC_OBJECT : 1,
  CF_KINEMATIC_OBJECT : 2,
  CF_NO_CONTACT_RESPONSE : 4,
  CF_CUSTOM_MATERIAL_CALLBACK : 8,//this allows per-triangle material (friction/restitution)
  CF_CHARACTER_OBJECT : 16,
  CF_DISABLE_VISUALIZE_OBJECT : 32, //disable debug drawing
  CF_DISABLE_SPU_COLLISION_PROCESSING : 64,//disable parallel/SPU processing
  CF_HAS_CONTACT_STIFFNESS_DAMPING : 128,
  CF_HAS_CUSTOM_DEBUG_RENDERING_COLOR : 256,
  CF_HAS_FRICTION_ANCHOR : 512,
  CF_HAS_COLLISION_SOUND_TRIGGER : 1024
}

let AmmoLib;

/**
 * 校验Ammo库是否存在
 */

function AmmoLibIsNotInit() {

  if( !AmmoLib ) {
    console.error("AmmoLib is not initialized. ");
    return true;
  }

  return false;

}
/**
 * 初始化Ammo库
 * 用法: await initAmmo()
 */
async function initAmmo() {

  if ( 'Ammo' in window === false ) {

    console.error( 'AmmoPhysics: Couldn\'t find Ammo.js' );
    return;

  }

  console.time("Initialize Ammo");
  AmmoLib = await Ammo(); 
  console.timeEnd("Initialize Ammo");
  
}

/**
 * 转换为Ammo类型
 */
function toAmmoType( obj ) {

  if( AmmoLibIsNotInit() ) return null;

  if( obj.isVector3 ) {
    return new AmmoLib.btVector3( obj.x, obj.y, obj.z );
  }

  if( obj.isQuaternion ) {
    return new AmmoLib.btQuaternion( obj.x, obj.y, obj.z, obj.w );
  }

  return null;

}

// position + quaternion转换为transform
function toAmmoTransform( position, quaternion ) {

  if( AmmoLibIsNotInit() ) return null;

  const transform = new AmmoLib.btTransform();
  transform.setIdentity();

  if( position ) {
    transform.setOrigin( toAmmoType( position ) );
  }

  if( quaternion ) {
    transform.setRotation( toAmmoType( quaternion ) );
  }

  return transform;

}
// 更新transform
function updateAmmoTransform( transform, position, quaternion ) {
  
  if( AmmoLibIsNotInit() ) return null;

  if( position ) {
    transform.setOrigin( toAmmoType( position ) );
  }

  if( quaternion ) {
    transform.setRotation( toAmmoType( quaternion ) );
  }

  return transform;

}

/**
 * 初始化Ammo物理引擎
 */
function initPhysics() {

  if( AmmoLibIsNotInit() ) return;

  console.time("Initialize Physics");

  const gravityConstant = settings.gravityConstant;
  const airDensityConstant = settings.airDensityConstant;

  const collisionConfiguration = new AmmoLib.btSoftBodyRigidBodyCollisionConfiguration();
  // const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration()
  const dispatcher = new AmmoLib.btCollisionDispatcher( collisionConfiguration );
  const broadphase = new AmmoLib.btDbvtBroadphase();
  const solver = new AmmoLib.btSequentialImpulseConstraintSolver();
  const softBodySolver = new AmmoLib.btDefaultSoftBodySolver();
  
  const physicsWorld = new AmmoLib.btSoftRigidDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration, softBodySolver );
  physicsWorld.setGravity( new AmmoLib.btVector3( 0, gravityConstant, 0 ) );
  physicsWorld.getWorldInfo().set_m_gravity( new AmmoLib.btVector3( 0, gravityConstant, 0 ) );
  physicsWorld.getWorldInfo().set_air_density ( airDensityConstant );

  // 设置prototype会导致物理模型频繁抖动？
  AmmoLib.btGImpactCollisionAlgorithm.prototype.registerAlgorithm(physicsWorld.getDispatcher());

  worldTransform = new AmmoLib.btTransform();
  worldTransform.setIdentity();

  console.timeEnd("Initialize Physics");

  return physicsWorld;

}

/**
 * 获取Mesh对应的形状
 * @param {*} mesh 网格
 * @param {*} scale 缩放
 * @param {*} isTriangle 是否为三角模型 
 * @returns 
 */
function getMeshShape( mesh, scale, isTriangle=true) {

  if( AmmoLibIsNotInit() ) return null;

  const margin = settings.margin;

  if ( !mesh.isMesh || !mesh.geometry ) {
    console.error( "不是一个合法的Mesh", mesh );
    return null;
  }

  if ( !scale ) {
    scale = new Vector3( 1, 1, 1 );
  }
  
  const geometry = mesh.geometry;
  const parameters = geometry.parameters;

  let shape = undefined;

  if (geometry.isBufferGeometry){

    const type = geometry.type;

    switch(type){

      case "BoxGeometry":

        var width = parameters.width !== undefined ? parameters.width / 2 : 0.5;
        var height = parameters.height !== undefined ? parameters.height / 2 : 0.5;
        var depth = parameters.depth !== undefined ? parameters.depth / 2 : 0.5;

        shape = new AmmoLib.btBoxShape( new AmmoLib.btVector3( width, height, depth ) );
        break;

      case "ConeGeometry":
  
        var radius = parameters.radius !== undefined ? parameters.radius : 1;
        var height = parameters.height !== undefined ? parameters.height : 1;

        shape = new AmmoLib.btConeShape( radius, height );
        break;

      case "CylinderGeometry":

        var height = parameters.height !== undefined ? parameters.height / 2 : 0.5;
        const radiusTop = parameters.radiusTop !== undefined ? parameters.radiusTop : 1;
        const radiusBottom = parameters.radiusBottom !== undefined ? parameters.radiusBottom : 1;

        shape = new AmmoLib.btCylinderShape( new AmmoLib.btVector3( radiusTop, height, radiusBottom ) );
        break;
      
      case "SphereGeometry":

        var radius = parameters.radius !== undefined ? parameters.radius : 1;

        shape = new AmmoLib.btSphereShape( radius );
        break;
      
      case "CapsuleGeometry":

        var radius = parameters.radius !== undefined ? parameters.radius : 1;
        var height = parameters.height !== undefined ? parameters.height : 1;

        shape = new AmmoLib.btCapsuleShape( radius, height );
        break;

      case "TorusGeometry":

        shape = createGImpactCollision( mesh, scale );
        // shape = createConvexHullPhysicsShape( geometry.attributes.position.array, scale );  

        break;

      case "PlaneGeometry":

        var width = parameters.width !== undefined ? parameters.width / 2 : .5;
        var height = parameters.height !== undefined ? parameters.height / 2 : .5;
        var depth = .002; // 默认厚度

        shape = new AmmoLib.btBoxShape( new AmmoLib.btVector3( width, depth, height ) );
        // shape = createGImpactCollision( mesh );

        break;

      default:
        if( isTriangle ){
          shape = createGImpactCollision( mesh, scale );
        }
        else {
          shape = createConvexHullPhysicsShape( geometry.attributes.position.array, scale );  
        }        
        break;
    }

  }
  else {

    console.error("不支持的几何形状:", geometry);

  }

  if( shape ) {
    shape.setMargin( margin );
  }

  return shape;

}

/**
 * 返回一个组合形状
 */
function compoundShape() {

  if( AmmoLibIsNotInit() ) return null;

  const shape = new AmmoLib.btCompoundShape();
  console.log( shape );

  shape.addChild = ( childShape, position, quaternion ) => {
    shape.addChildShape( toAmmoTransform( position, quaternion ), childShape);
  }

  shape.updateChild = ( childIndex, position, quaternion, shouldRecalculateLocalAabb=true  ) => {
    shape.updateChildTransform( childIndex, toAmmoTransform( position, quaternion, shouldRecalculateLocalAabb ) );
  }

  return shape;

}

// 获取mesh的顶点和面
function getMeshData( mesh ) {

  const index = mesh.geometry.index !== null ? mesh.geometry.index : undefined;
  const attributes = mesh.geometry.attributes;
  const scale = mesh.scale;

  if (attributes.position === undefined) {

      console.error('getMeshData(): Position attribute required for conversion.');
      return;

  }

  const position = attributes.position;

  let vertices = [];
  let faces = [];

  for (let i = 0; i < position.count; i++) {

      vertices.push({
          x: scale.x * position.getX(i),
          y: scale.y * position.getY(i),
          z: scale.z * position.getZ(i)
      });

  }

  if (index !== undefined) {

      for (let i = 0; i < index.count; i += 3) {

          faces.push({
              a: index.getX(i),
              b: index.getX(i + 1),
              c: index.getX(i + 2)
          });

      }

  } else {

      for (let i = 0; i < position.count; i += 3) {

          faces.push({
              a: i,
              b: i + 1,
              c: i + 2
          });

      }
  }

  return {
      vertices,
      faces
  }
}

// 生成凸包物理性状
function createConvexHullPhysicsShape( coords , scale ) {

  if( AmmoLibIsNotInit() ) return null;

  if ( !scale ) {
    scale = { x: 1, y: 1, z: 1 };
  }

  const shape = new AmmoLib.btConvexHullShape();
  var tempBtVec3_1 = new AmmoLib.btVector3( 0, 0, 0 );

  for ( let i = 0, il = coords.length; i < il; i += 3 ) {

    tempBtVec3_1.setValue( coords[ i ], coords[ i + 1 ], coords[ i + 2 ] );
    const lastOne = ( i >= ( il - 3 ) );
    shape.addPoint( tempBtVec3_1, lastOne );

  }

  shape.setLocalScaling(new AmmoLib.btVector3(scale.x, scale.y, scale.z));
  // shape.updateBound();
  // console.log(shape);

  return shape;

}

// 生成紧凑型碰撞
function createGImpactCollision( mesh, scale ) {

  if( AmmoLibIsNotInit() ) return null;

  let faces, vertices;
  let totvert = 0;

  if ( mesh.isMesh ) {
      let data = getMeshData(mesh);

      faces = data.faces;
      vertices = data.vertices;

      totvert = vertices.length;

  }
  else {
    console.error("cannot make mesh shape for non-Mesh object");
  }

  if (totvert == 0) {
    console.error("no vertices to define mesh shape with");
  }

  if ( !scale ) {
    scale = { x: 1, y: 1, z: 1 };
  }

  /* vertices, faces */
  let ammoMesh = new AmmoLib.btTriangleMesh();

  for (let i = 0, l = faces.length; i < l; i++) {
      let a = faces[i].a;
      let b = faces[i].b;
      let c = faces[i].c;
      ammoMesh.addTriangle(
          new AmmoLib.btVector3(vertices[a].x, vertices[a].y, vertices[a].z),
          new AmmoLib.btVector3(vertices[b].x, vertices[b].y, vertices[b].z),
          new AmmoLib.btVector3(vertices[c].x, vertices[c].y, vertices[c].z),
          false
      );
  }

  let triangleShape = new AmmoLib.btGImpactMeshShape(ammoMesh);
  triangleShape.setLocalScaling(new AmmoLib.btVector3(scale.x, scale.y, scale.z));
  triangleShape.updateBound();

  // console.log(triangleShape)

  return triangleShape;

}

// 生成刚体物理模型
function createRigidBody( physicsShape, mass, position, quaternion, inertia ) {

  if( AmmoLibIsNotInit() ) return null;

  const transform = new AmmoLib.btTransform();
  transform.setIdentity();
  transform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) );
  transform.setRotation( new AmmoLib.btQuaternion( quaternion.x, quaternion.y, quaternion.z, quaternion.w ) );
  const motionState = new AmmoLib.btDefaultMotionState( transform );
  
  const localInertia = new AmmoLib.btVector3( inertia.x, inertia.y, inertia.z );
  physicsShape.calculateLocalInertia( mass,  localInertia );

  const rbInfo = new AmmoLib.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
  const body = new AmmoLib.btRigidBody( rbInfo );

  if( mass > 0 ) {

    body.setActivationState( ActivationStates.DISABLE_DEACTIVATION );

  }
  
  return body;

}

/**
 * 生成铰链约束
 */
function createHingeConstraint( rbA, rbB, frameInA, frameInB, useReferenceFrameA=true ) {

  if( AmmoLibIsNotInit() ) return;

  const hinge = new AmmoLib.btHingeConstraint( 
    rbA, rbB, 
    frameInA, frameInB,
    useReferenceFrameA 
  );

  return hinge;

}

function createHingeConstraint2( rbA, rbB, pivotInA, pivotInB, axisInA, axisInB, useReferenceFrameA=true ) {

  if( AmmoLibIsNotInit() ) return;

  const hinge = new AmmoLib.btHingeConstraint( 
    rbA, rbB, 
    toAmmoType(pivotInA), toAmmoType(pivotInB),
    toAmmoType(axisInA), toAmmoType(axisInB),
    useReferenceFrameA 
  );

  return hinge;

}

/**
 * 生成约束
 * @param {*} config 约束配置文件
 */
function createConstraint( config ) {
  
  if( AmmoLibIsNotInit() ) return;

  const type = config.type;

  const rbA = config.rbA;
  const rbB = config.rbB;

  const originInA = new Vector3( config.originInA[0], config.originInA[1], config.originInA[2] );
  const rotationInA = new Quaternion().setFromEuler( new Euler( config.rotationInA[0], config.rotationInA[1],config.rotationInA[2] ));
  const originInB = new Vector3( config.originInB[0], config.originInB[1], config.originInB[2] );
  const rotationInB = new Quaternion().setFromEuler( new Euler( config.rotationInB[0], config.rotationInB[1],config.rotationInB[2] ));
  
  const frameInA = new AmmoLib.btTransform();
  frameInA.setOrigin( toAmmoType( originInA ) );
  frameInA.setRotation( toAmmoType( rotationInA ) );

  const frameInB = new AmmoLib.btTransform();
  frameInB.setOrigin( toAmmoType( originInB ) );
  frameInB.setRotation( toAmmoType( rotationInB ) );

  const useReferenceFrameA = config.useReferenceFrameA;

  let constraint;

  
  switch( type ){
    case "hinge":
      constraint = createHingeConstraint( rbA, rbB, frameInA, frameInB, useReferenceFrameA );
      // constraint.setLimit( 0, 0 , 0.1, 0.01, 0.01 );
      constraint.enableAngularMotor( true, 0, 9999 );
      break;
    default:break;
  }

  return constraint;

}

/**
 * 更新物体的刚体模型
 */
function updateRigidBody( obj ) {

  const shape = obj.physicsShape;
  const mass = obj.mass;
  const position = obj.position;
  const quaternion = obj.quaternion;
  const inertia = obj.inertia;

  let body;

  if( obj.isA3ObjectGroup ) {
    body = [];

    if( obj.enableBaseShape ) {
      const baseBody = createRigidBody( obj.physicsShape[0], mass, position, quaternion, inertia );
      body.push( baseBody );

      createUserPointer( obj.threeObject, baseBody );
    }

    for( var i = 0, il = obj.children.length; i < il; i++ ) {
      body.push( updateRigidBody( obj.children[i] ) );
    }  
  }
  else {
    body = createRigidBody( shape, mass, position, quaternion, inertia );
    body.setFriction( obj.friction );
    body.setRestitution( obj.restitution );

    createUserPointer( obj.threeObject, body );

  }

  obj.physicsBody = body;

  return body;

}

/**
 * 取消重力
 */
function disableGravity( body ) {

  if( AmmoLibIsNotInit() ) return;

  body.applyGravity( new AmmoLib.Vector3(0, 0, 0) );

}

/**
 * 建立3D模型和物理模型的联系
 * @param threeObject 
 * @param physicsBody 
 * @returns 
 */
function createUserPointer( threeObject, physicsBody ) {

  if( AmmoLibIsNotInit() ) return;

  threeObject.userData.physicsBody = physicsBody;

  const btVecUserData = new AmmoLib.btVector3( 0, 0, 0 );
  btVecUserData.threeObject = threeObject;
  physicsBody.setUserPointer( btVecUserData );

}

/**
 * 获取物理实体对应的3D物体
 * @param {*} body 
 */
function getThreeObject( body ){

  if( AmmoLibIsNotInit() ) return;

  const threeObject = AmmoLib.castObject( body.getUserPointer(), AmmoLib.btVector3 ).threeObject;

  return threeObject;

}

/**
 * 获取3D物体对应的物理实体
 * @param {*} threeObject 
 */
function getPhysicsBody( threeObject ) {

  return threeObject.userData.physicsBody;

}

/**
 * 创建物体物理形状
 */
function createPhysicsShape( obj ) {

  let shape;

  if( obj.isA3ObjectGroup ) {
    
    const children = obj.children;
    // shape = compoundShape();
    shape = [];

    if( obj.enableBaseShape ) {
      const baseShape = AmmoHelper.getMeshShape( obj.collider );
      shape.push( baseShape );
    }
    
    for( var i = 0, il = children.length; i < il; i++ ) {
      createPhysicsShape( children[i] );
      // shape.addChild( children[i].physicsShape, children[i].position, children[i].quaternion );
      shape.push( children[i].physicsShape );
    }

    obj.physicsShape = shape;

    return shape;

  }
  else {

    if ( obj.hasUserTransform ) {
            
      shape = compoundShape();
      const childShape = getMeshShape( obj.collider );
      shape.addChild( childShape, obj.userPosition, obj.userQuaternion );
      
    }
    else {
      shape = AmmoHelper.getMeshShape( obj.collider );
    }

    obj.physicsShape = shape;
  }

  return shape;

}

//////////////////////////////////////////////////// exports 
AmmoHelper.AmmoLib = AmmoLib;
AmmoHelper.compoundShape = compoundShape;
AmmoHelper.initAmmo = initAmmo;
AmmoHelper.initPhysics = initPhysics;
AmmoHelper.getMeshShape = getMeshShape;
AmmoHelper.updateRigidBody = updateRigidBody;
AmmoHelper.createPhysicsShape = createPhysicsShape;
AmmoHelper.createConstraint = createConstraint;
AmmoHelper.disableGravity = disableGravity;

export default AmmoHelper;

export {

  ActivationStates,
  CollisionFlags,
  AmmoLib,
  worldTransform,

  compoundShape,

  initAmmo,
  initPhysics,
  toAmmoType,
  toAmmoTransform,
  updateAmmoTransform,

  getThreeObject,
  getPhysicsBody,

}